#!/usr/bin/env python
#coding=utf-8
import os
import sqlite3
import shutil
import datetime

class Product(dict):
    def __init__(self, db_info, idx, mgr):
        self["product"] = db_info["product"]
        self["version"] = db_info["version"]
        self["db"] = db_info["db"]
        self["id"] = idx
        self._mgr = mgr
        self._loaded = False
        self._loadedTime = None
        self._accessTime = None
        self.fullName = self["product"] + "/" + self["version"]

    def __del__(self):
        self.unload_product()

    def getProductMgr(self):
        return self._mgr

    def product_dir(self):
        pass

    def unload_product(self):
        if not self._loaded:
            return
        print("Unload product %s/%s now ..." % (self["product"], self["version"]))
        self._cursor.close()
        self._db.close()
        self._loaded = False

    def getCursor(self):
        if not self.load_db():
            return None
        return self._cursor

    def __create_views(self):
        sqlcmd = "drop view if exists symbols_count"
        self._cursor.execute(sqlcmd)

        sqlcmd = "create view IF NOT EXISTS [symbols_count] as SELECT symbols.parent_id, symbols.name, symbols.weak, symbols.version, symbols.library, symbols.demangle, symbols.id, calls_count.count from symbols LEFT OUTER join calls_count on symbols.id=calls_count.symbol_id"
        self._cursor.execute(sqlcmd)

    def load_db(self):
        if not os.path.exists(self["db"]):
            # Deleted
            self.unload_product()
            return False
        if self._loaded == True:
            self._accessTime = datetime.datetime.now()
            return True

        self._db = sqlite3.connect(self["db"])
        self._cursor = self._db.cursor()
        self._loaded = True
        self._loadedTime = datetime.datetime.now()
        self._accessTime = self._loadedTime

        self.__create_views()
        return True

    def unload_if_inactive(self):
        if not self._loaded:
            return

        now = datetime.datetime.now()
        time_diff = now - self._accessTime
        print("Check product %s/%s [load: %s] [access:%s] diff %d now ..." % (self["product"], self["version"], self._loadedTime, self._accessTime, time_diff.total_seconds()))
        if time_diff.total_seconds() > 24 * 3600: # 24 hours
            self.unload_product()

def product_cmp(x, y):
    if x["product"] == y["product"]:
        if x["version"] > y["version"]:
            return -1
        else:
            return 1
    if x["product"] < y["product"]:
        return -1
    return 1

class ProductMgr:
    PRODUCT_DB_FILE_NAME = "archinfo.db"
    PRODUCT_DB_DIR = "db"
    PRODUCT_STAGING_DIR = "staging"

    def __init__(self, root_path, product_class=None):
        self._root_path = os.path.normpath(root_path)
        self.products = []
        self._product_class = product_class
        self._idx = 0
        if not product_class:
            self._product_class = Product
        self._scan_products()

    def get_root_path(self):
        return self._root_path

    def __unload_inactive_products(self):
        for pro in self.products:
            pro.unload_if_inactive()

    def get_all(self):
        self._update_from_staging_dir()
        self.__unload_inactive_products()
        return self.products

    def find_by_id(self, id):
        idx = int(id)
        if idx >= len(self.products) or idx < 0:
            return None
        return self.products[idx]

    def get_product_by_name(self, product, version):
        for pro in self.products:
            if pro["product"] == product and pro["version"] == version:
                if pro.load_db():
                    return pro
                # Product has been removed
                self.products.remove(pro)
                return None
        return None

    def _scan_products(self):
        dbDir = os.path.join(self._root_path, ProductMgr.PRODUCT_DB_DIR)
        try:
            os.makedirs(dbDir)
        except:
            pass
        dbs = ProductMgr._scan_db_files(dbDir)
        for idx, db in enumerate(dbs):
            self.products.append(self._product_class(db, self._idx, self))
            self._idx = self._idx + 1

        self.products = sorted(self.products, key = lambda product: product.fullName, reverse = True)

    def _update_from_staging_dir(self):
        stagingDir = os.path.join(self._root_path, ProductMgr.PRODUCT_STAGING_DIR)
        try:
            os.makedirs(stagingDir)
        except:
            pass
        stagings = ProductMgr._scan_db_files(stagingDir)
        if len(stagings) == 0:
            return

        print("Moving %d staging archinfo.db ..." % len(stagings))
        for stage in stagings:
            dst = os.path.join(self._root_path, ProductMgr.PRODUCT_DB_DIR, stage["product"], stage["version"], ProductMgr.PRODUCT_DB_FILE_NAME)
            src = stage["db"]

            dst_dir = os.path.dirname(dst)
            if not os.path.exists(dst_dir):
                os.makedirs(dst_dir)

            shutil.move(src, dst)
            stage["db"] = dst

            self.products.append(self._product_class(stage, self._idx, self))
            self._idx = self._idx + 1

            try:
                shutil.rmtree(os.path.dirname(src))
            except:
                pass

        self.products = sorted(self.products, key = lambda product: product.fullName, reverse = True)

    @staticmethod
    def _scan_db_files(path):
        results = []
        subdirs = os.listdir(path)
        for product in subdirs:
            curDir = os.path.join(path, product)
            if not os.path.isdir(curDir):
                continue
            children = os.listdir(curDir)
            for ver in children:
                childDir = os.path.join(curDir, ver)
                if not os.path.isdir(childDir):
                    continue
                db_file = os.path.join(childDir, ProductMgr.PRODUCT_DB_FILE_NAME)
                if not os.path.exists(db_file):
                    continue
                results.append({"product": product, "version": ver, "db": db_file})
        return results

if __name__ == "__main__":
    product_mgr = ProductMgr(".")
    print(product_mgr.get_all())
